﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Diagnostics;
using System.Threading;

namespace Microsoft.Isam.Esent.Serialization
{
	/// <summary>This class maintains the pool of the ESENT sessions, to serve ASP.NET requests coming to your web application.</summary>
	/// <remarks><para>You should not attempt to create more then one AspSessionPool instance in the same process:
	/// both database location, and DB instance name are hard-coded.</para>
	/// <para>This class encapsulates a <see cref="SessionPool" /> instance.
	/// In the same process, you may use either <see cref="EseSerializer"/>, <see cref="SessionPool"/>, or AspSessionPool, but not both.</para>
	/// <para>You should store the only instance of AspSessionPool object
	/// in your <see cref="System.Web.HttpApplicationState">Application</see>.</para>
	/// <para>The database is located in "~/App_Data/ESENT" folder.<br />
	/// This class uses <see cref="Subst" /> to mount "~/App_Data" to a drive letter.<br />
	/// NTFS encryption is not used for the database.</para>
	/// <para><b>NB:</b> only use this class within an ASP.NET web application.</para></remarks>
	public class AspSessionPool
	{
		/// <summary>The virtual path where to keep the database.</summary>
		const string s_FolderVirtualPath = "~/App_Data/ESENT";

		/// <summary>Key of the request' session in the HttpContext.Current.Items collection.</summary>
		const string s_EsentSessionRequestKey = "EsentSession";

		SessionPool pool;

        /// <summary>Set this action to your method that upgrades the DB schema.</summary>
        /// <remarks>The delegate will be called immediately after the first session is constructed.</remarks>
        public Action<DatabaseSchemaUpdater> updateSchema 
        {
            get { return this.pool.updateSchema; }
            set { this.pool.updateSchema = value; }
        }

		/// <summary>Construct the session pool.</summary>
		/// <remarks>You should store the only instance of AspSessionPool object
		/// in your <see cref="System.Web.HttpApplicationState">Application</see>.</remarks>
		/// <param name="arrTypes">The array of record types.
		/// In every session, the corresponding tables will be already opened for you.</param>
		public AspSessionPool( params Type[] arrTypes )
		{
			string strFolder = System.Web.Hosting.HostingEnvironment.MapPath( s_FolderVirtualPath );
			pool = new SessionPool( strFolder, false, true, 0, arrTypes );
		}

		/// <summary>Construct the pool.</summary>
		/// <param name="maxSessions">Maximum count of the sessions to open, 0 = unlimited.</param>
		/// <param name="arrTypes">The array of record types.
		/// In every session, the corresponding tables will be already opened for you.</param>
		public AspSessionPool( int maxSessions, params Type[] arrTypes )
		{
			string strFolder = System.Web.Hosting.HostingEnvironment.MapPath( s_FolderVirtualPath );
			pool = new SessionPool( strFolder, false, true, maxSessions, arrTypes );
		}

		/// <summary>Close all sessions, and the whole database as well.</summary>
		/// <param name="context">The HttpContext is used for tracing.</param>
		/// <remarks><para>You should dispose the AspSessionPool when your application shuts down.</para>
		/// <para>One way to do that - write the similar code in your Global.asax:</para>
		///<code lang="C#">            void Application_End( object sender, EventArgs e )
		///{
		///	// Dispose the AspSessionPool object.
		///	var sp = Application[ appkeySessionPool ] as Microsoft.Isam.Esent.Serialization.AspSessionPool;
		///	if( null != sp )
		///	{
		///		sp.Dispose( Context );
		///		Application[ appkeySessionPool ] = null;
		///	}
		///}</code></remarks>
		public void Dispose( HttpContext context )
		{
			// Release the session for this request.
			try
			{
				var req = context.Items;
				SessionPool.iPooledSession sess = req[ s_EsentSessionRequestKey ] as SessionPool.iPooledSession;
				if( null != sess )
				{
					sess.ResetReferenceCounter( 1 );
					sess.Release();
				}
			}
			catch( System.Exception ex )
			{
				if( null != context && null != context.Trace )
					context.Trace.Warn( "AspSessionPool.Dispose", "Unable to close the current session", ex );
			}

			// Dispose the session pool.
			if( null != pool )
			{
				pool.Dispose();
				pool = null;
			}
		}

		/// <summary>Get the ESENT session from the pool.</summary>
		/// <returns>Return the session.</returns>
		/// <param name="context"></param>
		/// <remarks>
		/// <para>If context.Items contains the existing ESENT session, the old session is returned to the caller.</para>
		/// <para>Even if context.Items does not contain the session, this operation is inexpensive: usually the method returns the last session released to the pool.</para>
		/// <para><b>NB:</b> You must call <see cref="IDisposable.Dispose">Dispose</see> exactly once
		/// for each iSerializerSession instance returned by this method. The recommended construction is:</para>
		/// <code lang="C#">            using( iSerializerSession sess = m_sessionPool.GetSession( context ) )
		///using( iSerializerTransaction trans = sess.BeginTransaction() )
		///{
		///	// Do whatever you need with the data: obtain cursors/recordsets, search, modify records, etc..
		///	trans.Commit();		// Or don't, if you need the transaction to roll back.
		///}</code>
		/// </remarks>
		public iSerializerSession GetSession( HttpContext context )
		{
			System.Collections.IDictionary req = null;

			if( null != context )
			{
				req = context.Items;
				if( req.Contains( s_EsentSessionRequestKey ) )
				{
					var existing = req[ s_EsentSessionRequestKey ] as SessionPool.iPooledSession;
					int rcEx = existing.AddRef();
					context.Trace.Write( "AspSessionPool", "GetSessionImpl: returning an existing session, refCount=" + rcEx.ToString() );
					return existing as iSerializerSession;
				}
			}

			var result = pool.GetSession();

			if( null != req )
				req.Add( s_EsentSessionRequestKey, result );

			return result;
		}
	}
}